// --------------------------------------------------------------------------------------------------------------------- 
// <copyright file="AggregateRule.cs" company="Scrum for Team System">
//   None
// </copyright>
// <summary>
//   Defines the AggregateRule type.
// </summary>
// ---------------------------------------------------------------------------------------------------------------------
namespace ScrumForTeamSystem.EventService.AggregationService.DataObjects
{
    using System.Collections.ObjectModel;
    using System.Globalization;
    using System.Linq;
    using System.Text;
    using System.Xml.Serialization;

    using Infrastructure;

    using Properties;

    /// <summary>
    /// Defines the aggregation types.
    /// </summary>
    public enum AggregationType
    {
        /// <summary>
        /// Sum the aggregate components.
        /// </summary>
        Sum = 0,

        /// <summary>
        /// Use the minimum value.
        /// </summary>
        Min = 1,

        /// <summary>
        /// Use the maximum value.
        /// </summary>
        Max = 2,

        /// <summary>
        /// Use the average value.
        /// </summary>
        Average = 3
    }

    /// <summary>
    /// Defines the AggregateRule type.
    /// </summary>
    public class AggregateRule
    {
        /// <summary>
        /// The excluded source states collection.
        /// </summary>
        private readonly Collection<string> excludedSourceStates = new Collection<string>();

        /// <summary>
        /// The description field.
        /// </summary>
        private string description;

        /// <summary>
        /// The source work item query field.
        /// </summary>
        private string sourceWorkItemQuery;

        /// <summary>
        /// The target work item query field.
        /// </summary>
        private string targetWorkItemQuery;

        /// <summary>
        /// Gets or sets the work item source and target.
        /// </summary>
        /// <value>The type of the source and target work items.</value>
        public SourceTargetPair WorkItemType
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the rule link.
        /// </summary>
        /// <value>The rule link.</value>
        public WorkItemLink Link
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the inferred link.
        /// </summary>
        /// <value>The inferred link.</value>
        public InferredLink InferredLink
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the field.
        /// </summary>
        /// <value>The field.</value>
        public SourceTargetPair Field
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the history message.
        /// </summary>
        /// <value>The change message.</value>
        public string ChangeNote
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the type of the aggregate.
        /// </summary>
        /// <value>The type of the aggregate.</value>
        [XmlAttribute(AttributeName = "type")]
        public AggregationType AggregateType
        {
            get;
            set;
        }

        /// <summary>
        /// Gets the excluded states.
        /// </summary>
        /// <value>The excluded states.</value>
        [XmlArray]
        [XmlArrayItem(ElementName = "State")]
        public Collection<string> ExcludedSourceStates
        {
            get 
            {
                return this.excludedSourceStates; 
            }
        }

        /// <summary>
        /// Gets the description.
        /// </summary>
        /// <value>The description.</value>
        public string Description
        {
            get
            {
                return this.description = this.description ?? GetRuleDescription(this);
            }
        }

        /// <summary>
        /// Gets the source work item query.
        /// </summary>
        /// <value>The source work item query.</value>
        public string SourceWorkItemQuery
        {
            get
            {
                return this.sourceWorkItemQuery = this.sourceWorkItemQuery ?? GetQuery(this, true); 
            }
        }

        /// <summary>
        /// Gets the target work item query.
        /// </summary>
        /// <value>The target work item query.</value>
        public string TargetWorkItemQuery
        {
            get
            {
                return this.targetWorkItemQuery = this.targetWorkItemQuery ?? GetQuery(this, false);
            }
        }

        /// <summary>
        /// Gets the rule description.
        /// </summary>
        /// <param name="rule">The aggregation rule.</param>
        /// <returns>The rule detail as a concatentated string.</returns>
        private static string GetRuleDescription(AggregateRule rule)
        {
            var sb = new StringBuilder();

            sb.Append("Aggregate Rule: (");
            sb.Append("WorkItemTypes - '");
            sb.Append(rule.WorkItemType.Source);
            sb.Append("' => '");
            sb.Append(rule.WorkItemType.Target);
            sb.Append("')");

            if (rule.Link != null)
            {
                sb.Append("; Link: '");
                sb.Append(rule.Link.LinkTypeName);
                sb.Append("' (Target on forward end: ");
                sb.Append(rule.Link.IsTargetOnForwardLink);
                sb.Append(")");
            }
            else if (rule.InferredLink != null)
            {
                sb.Append("; Inferred Link: '");
                sb.Append(rule.InferredLink.PathType);
                sb.Append("' - Match Type: '");
                sb.Append(rule.InferredLink.MatchType);
                sb.Append("'");

                if (rule.InferredLink.MatchType == PathMatch.Partial)
                {
                    sb.Append(" (Depth: ");
                    sb.Append(rule.InferredLink.MatchDepth);
                    sb.Append(")");
                }
            }

            sb.Append("; Field - '");
            sb.Append(rule.Field.Source);
            sb.Append("' => '");
            sb.Append(rule.Field.Target);

            sb.Append("'; Excluded Source States: (");
            var isFirstLoop = true;
            foreach (var state in rule.ExcludedSourceStates)
            {
                if (isFirstLoop)
                {
                    isFirstLoop = false;
                }
                else
                {
                    sb.Append(", ");
                }

                sb.Append("'");
                sb.Append(state);
                sb.Append("'");
            }

            sb.Append(")");

            return sb.ToString();
        }

        /// <summary>
        /// Gets the source WIQL query.
        /// </summary>
        /// <param name="rule">The calc rule.</param>
        /// <param name="getSourceItems">if set to <c>true</c> [get source].</param>
        /// <returns>WIQL for matched work items.</returns>
        private static string GetQuery(AggregateRule rule, bool getSourceItems)
        {
            string query, stateFilter, fieldName, workItemType;

            if (getSourceItems)
            {
                stateFilter = GetExcludedSourceStateFilter(rule);
                fieldName = rule.Field.Source;
                workItemType = rule.WorkItemType.Source;
            }
            else
            {
                stateFilter = string.Empty;
                fieldName = rule.Field.Target;
                workItemType = rule.WorkItemType.Target;
            }

            if (rule.Link != null)
            {
                query = string.Format(
                        CultureInfo.InvariantCulture,
                        Settings.Default.GetLinkedItemsQuery,
                        fieldName,
                        "{0}",
                        workItemType,
                        stateFilter);
            }
            else if (rule.InferredLink != null)
            {
                var pathOperator = getSourceItems ? "UNDER" : "=";

                query = string.Format(
                        CultureInfo.InvariantCulture,
                        Settings.Default.GetInferredLinksQuery,
                        fieldName,
                        rule.InferredLink.PathFieldName,
                        pathOperator,
                        "{0}",
                        workItemType,
                        stateFilter);
            }
            else
            {
                throw new ServiceException(
                        Settings.Default.ServiceName, "Rule invalid. No linkage element specified.");
            }

            return query;
        }

        /// <summary>
        /// Gets the state filter.
        /// </summary>
        /// <param name="rule">The calc rule.</param>
        /// <returns>A WIQL fragment to filter on exluded startes.</returns>
        private static string GetExcludedSourceStateFilter(AggregateRule rule)
        {
            var stateFilter = string.Empty;

            if (rule.ExcludedSourceStates != null && rule.ExcludedSourceStates.Count() != 0)
            {
                var states = string.Empty;

                foreach (var state in rule.ExcludedSourceStates)
                {
                    states = string.Concat(states, string.IsNullOrEmpty(states) ? string.Empty : ",", "'", state, "'");
                }

                stateFilter = string.Format(CultureInfo.InvariantCulture, Settings.Default.StateFilter, states);
            }

            return stateFilter;
        }
    }
}